---
title: Migration to v0.8
---

import { Callout } from "fumadocs-ui/components/callout";

<Callout>
  This migration guide is for an upcoming release. More changes may land before
  v0.8.0 is released.
</Callout>

## Styled Components moved to @assistant-ui/react-ui

All styled components (Thread, ThreadList, AssistantModal, makeMarkdownText, etc.) have been moved to a new package, `@assistant-ui/react-ui`.

To migrate, use the migration codemod:

```sh
# IMPORTANT: make sure to commit all changes to git / creating a backup before running the codemod
npx assistant-ui upgrade
```

## Vercel AI SDK RSC requires additional setup

Built-in RSC support in assistant-ui has been removed, so an additional setup step is required.
The RSC runtime now requires additional setup to display React Server Components.

```ts
import { RSCDisplay } from "@assistant-ui/react-ai-sdk";

// if you are using the default Thread component
// add RSCDisplay to assistantMessage.components.Text
<Thread assistantMessage={{ components: { Text: RSCDisplay } }} />


// if you are using unstyled primitives, update MyThread.tsx
<MessagePrimitive.Content components={{ Text: RSCDisplay }} />
```

## Migrate away from UIContentPart

For instructions on migrating for Vercel AI SDK RSC, see section above.
This migration guide is for users of `useExternalStoreRuntime`.

### Recommended Approach: Use ToolUI

First, reconsider your approach.

Creating UI components in the `convertMessage` callback is considered an anti-pattern.
The recommended alternative approach is to pass tool-call content parts, and use `makeAssistantToolUI` to map these tool calls to UI components.

This ensures that the data layer is separate and decoupled from the UI layer.

#### Example

Consider the following example, where you are using a UIContentPart to show a loading indicator.

```ts title="bad.ts"
// THIS IS BAD
const convertMessage = (message: MyMessage): ThreadMessageLike => {
  if (message.isLoading) {
    return { content: [{ type: "ui", display:< MyLoader /> }] };
  }
  // ...
};
```

```ts title="good.ts"
const convertMessage = (message: MyMessage): ThreadMessageLike => {
  if (message.isLoading) {
    return { content: [] };
  }
  // ...
};

// use the empty content part to show the loading indicator
<Thread assistantMessage={{ components: { Empty: MyLoader } }} />;
```

(if you are using unstyled primitives, update MyThread.tsx, and pass the component to MessagePrimitive.Content)

#### Example 2

Consider the following example, where you are displaying a custom chart based on data received from an external source.

```ts title="bad.ts"
// THIS IS BAD
const convertMessage = (message: MyMessage): ThreadMessageLike => {
  return { content: [{ type: "ui", display: <MyChart data={message.chartData} /> }] };
};
```

```ts title="good.ts"
const convertMessage = (message: MyMessage): ThreadMessageLike => {
  return {
    content: [
      {
        type: "tool-call",
        toolName: "chart",
        args: message.chartData,
      },
    ],
  };
};

const ChartToolUI = makeAssistantToolUI({
  toolName: "chart",
  render: ({ args }) => <MyChart data={args} />,
});

// use tool UI to display the chart
<Thread tools={[ChartToolUI]} />;
```

(if you are using unstyled primitives, render the `<ChartToolUI />` component anywhere inside your AssistantRuntimeProvider)

### Fallback Approach: Override ContentPartText

However, sometimes you receive UI components from an external source.

The example below assumes that your custom `MyMessage` type has a `display` field.

First, we define a dummy `UI_PLACEHOLDER` content part, which we will replace with the UI component later:

```ts
const UI_PLACEHOLDER = Object.freeze({
  type: "text",
  text: "UI content placeholder",
});
const convertMessage = (message: MyMessage): ThreadMessageLike => ({
  content: [
    // other content parts,
    UI_PLACEHOLDER,
  ],
});
```

Then, we define a custom `TextContentPartComponent`:

```tsx
const MyText: TextContentPartComponent = () => {
  const isUIPlaceholder = useContentPart((p) => p === UI_PLACEHOLDER);

  // this assumes that you have a `display` field on your original message objects before conversion.
  const ui = useMessage((m) =>
    isUIPlaceholder ? getExternalStoreMessage(m).display : undefined,
  );
  if (ui) {
    return ui;
  }

  return <MarkdownText />; // your default text component
};
```

We pass this component to our Thread:

```tsx
<Thread
  assistantMessage={{ components: { Text: MyText } }}
  userMessage={{ components: { Text: MyText } }}
/>
```

(if you are using unstyled primitives, update MyThread.tsx, and pass the component to MessagePrimitive.Content)

Now, the `UI_PLACEHOLDER` content part is replaced with the UI component we defined earlier.
